/**
 *
 */
import scala.collection.{ mutable => mutable }
import mutable.ArraySeq
import scala.collection.mutable.SynchronizedMap
import Trivalente._

/**
 * @author Gustavo
 *
 */
class Mundo {
	val TAMAÑO_INICIAL_MUNDO = 100
	val TAMAÑO_MUNDO_MINIMO = 31

	private object celdas {
		private[this] val celdas: SeqIndexadaBidireccional[SeqIndexadaBidireccional[Celda]] = new SeqIndexadaBidireccional(new SeqIndexadaBidireccional(Desconocida, TAMAÑO_INICIAL_MUNDO), TAMAÑO_INICIAL_MUNDO)

		def apply(x: Int, y: Int): Celda = this.synchronized { celdas(y)(x) }
		def update(x: Int, y: Int, oc: Celda): Unit = this.synchronized { celdas(y)(x) = oc }
		@inline def apply(p: XY): Celda = apply(p.x, p.y)
		@inline def update(p: XY, oc: Celda): Unit = update(p.x, p.y, oc)

		private[this] var ejeXEstaEmpalmado_ = false
		def ejeXEstaEmpalmado = this.ejeXEstaEmpalmado_

		private[this] var ejeYEstaEmpalmado_ = false
		def ejeYEstaEmpalmado = this.ejeYEstaEmpalmado_

		//		/**Se fija si hubo un wrap en la direccion recibida, y en caso afirmativo empalma el eje correspondiente y da true*/
		//		def siHuboWrapEmpalmar(direccion: XY, salto: XY): Boolean = this.synchronized {
		//			val proyeccionSalto: Int = direccion * salto
		//			if (proyeccionSalto >= TAMAÑO_MUNDO_MINIMO) {
		//				val periodoRepeticion = averiguarPeriodoRepeticion(direccion, proyeccionSalto)
		//				periodoRepeticion match {
		//					case Some(p) => { empalmar(direccion, p); true }
		//					case None => false
		//				}
		//			} else
		//				false
		//		}

		def chequearPeriodoRepeticion(versorEje: XY, periodo: Int): Trivalente = this.synchronized {
			def chequearEncajeEntreDosCeldas(c1: Celda, c2: Celda): Trivalente = c1 match {
				case Desconocida => Trivalente.Incierto
				case Piso(_) => c2 match {
					case Piso(_) => Trivalente.Verdadero
					case Pared() => Trivalente.Falso
					case _ => Trivalente.Incierto
				}
				case Pared() => c2 match {
					case Piso(_) => Trivalente.Falso
					case Pared() => Trivalente.Verdadero
					case _ => Trivalente.Incierto
				}
				case _ => Trivalente.Incierto
			}
			def chequearEncajeEntreDosFilas(f1: SeqIndexadaBidireccional[Celda], f2: SeqIndexadaBidireccional[Celda]): Trivalente = {
				val base = scala.math.max(f1.base, f2.base)
				val tope = scala.math.min(f1.tope, f2.tope)
				var encajan: Trivalente = Trivalente.Incierto
				var i: Int = base
				while (i < tope && encajan != Falso)
					encajan = encajan &/\| chequearEncajeEntreDosCeldas(f1(i), f2(i))
				encajan
			}
			def chequearRepeticion[T](seq: SeqIndexadaBidireccional[T], periodo: Int)(chequeador: (T, T) => Trivalente): Trivalente = {
				var i1 = seq.base
				var i2 = i1 + periodo
				var hayRepeticion: Trivalente = Trivalente.Incierto
				while (i2 < seq.tope && hayRepeticion != Falso) {
					val c1 = seq(i1)
					val c2 = seq(i2)
					hayRepeticion = hayRepeticion &/\| chequeador(c1, c2)
					i1 += 1; i2 += 1
				}
				hayRepeticion
			}
			def chequearRepeticionHorizontalEnPlano(periodo: Int): Trivalente = {
				var hayRepeticion: Trivalente = Trivalente.Incierto
				val iterador = celdas.iterator
				while (hayRepeticion != Falso && iterador.hasNext) {
					hayRepeticion = hayRepeticion &/\| chequearRepeticion(iterador.next(), periodo)(chequearEncajeEntreDosCeldas)
				}
				hayRepeticion
			}

			if (versorEje == XY.Right)
				chequearRepeticionHorizontalEnPlano(periodo)
			else
				chequearRepeticion(celdas, periodo)(chequearEncajeEntreDosFilas)
		}

		def empalmar(versorEje: XY, periodoRepeticion: Int): Unit = this.synchronized {

			def fundirCeldas(c1: Celda, c2: Celda): Celda = {
				//TODO
			}
			def fundirFilas(f1: SeqIndexadaBidireccional[Celda], f2: SeqIndexadaBidireccional[Celda]): SeqIndexadaBidireccional[Celda] = {
				//TODO
			}
			def trasladar[T](fila: SeqIndexadaBidireccional[T], desde: Int, largo: Int, hacia: Int)(fundir: (T, T) => T) {
				// TODO
			}

			def empalmarEjeX(nuevaBase: Int, nuevoTope: Int) {
				for (fila <- celdas) {
					if (fila.base < nuevaBase) {
						val largoFragmento = nuevaBase - fila.base
						trasladar(fila, fila.base, largoFragmento, nuevoTope - largoFragmento)(fundirCeldas)
						fila.setWrap(nuevaBase, periodoRepeticion)
					}
					if (fila.tope > nuevoTope) {
						val largoFragmento = fila.tope - nuevoTope
						trasladar(fila, nuevoTope, largoFragmento, nuevaBase)(fundirCeldas)
						fila.setWrap(nuevaBase, periodoRepeticion)
					}
				}

			}

			def empalmarEjeY(nuevaBase: Int, nuevoTope: Int) {
				// TODO
			}

			val nuevaBase = periodoRepeticion / 2
			val nuevoTope = nuevaBase + periodoRepeticion
			if (versorEje == XY.Right)
				empalmarEjeX(nuevaBase, nuevoTope)
			else
				empalmarEjeY(nuevaBase, nuevoTope)
		}

		override def toString = {
			val ejeX = Range(celdas.map(_.base).reduce(scala.math.min(_, _)), celdas.map(_.tope).reduce(scala.math.max(_, _)))
			"\n" + celdas.zipWithIndex.map(t => ejeX.map(t._1(_).toString).reduce(_ + _) + " " + t._2.toString + "\n").reduce(_ + _)
		}
	}

	override def toString = celdas.toString

	private var horaActual: Int = 0
	private var cartografoMaestro_posicionProcurada: XY = null
	private var cartografoMaestro_direccionMovProcurado: XY = null
	private var cartografoMaestro_perdido: Boolean = false
	private var cartografoMaestro_nombre: String = null
	private var cartografoMaestro: Cartografo = null

	def getCartografoAjustadoYActualizarPlano(name: String, isMaster: Boolean, time: Int, view: View, offsetToMaster: XY): Cartografo = this.synchronized {
		this.synchronized {
			if (time != horaActual) {
				horaActual = time;
				cartografoMaestro_posicionProcurada = cartografoMaestro.posicionProcurada
				cartografoMaestro_direccionMovProcurado = cartografoMaestro.direccionMovProcurado
				cartografoMaestro_perdido = cartografoMaestro.perdido
			}
		}

		// averiguar posicion del centro de la vista
		val cartografo = if (time == 0) {
			cartografoMaestro = new Cartografo(XY.Zero)
			cartografoMaestro_nombre = name
			System.out.println("time=" + time + ", name=" + name + ", pos=" + cartografoMaestro.getPosicionRelativaAOrigenMundo + ", view=" + view)
			cartografos.put(name, cartografoMaestro)
			cartografoMaestro
		} else if (isMaster) {
			System.out.println("time=" + time + ", name=" + name + ", pre actualizar: pos=" + cartografoMaestro.getPosicionRelativaAOrigenMundo + ", perdido=" + cartografoMaestro.perdido + ", view=" + view)
			cartografoMaestro.actualizarPosicion(view, XY.Zero) // TODO crear clases CartografroMaestro y CartografroComun
			System.out.println("time=" + time + ", name=" + name + ", pos actualizar: pos=" + cartografoMaestro.getPosicionRelativaAOrigenMundo + ", perdido=" + cartografoMaestro.perdido)
			cartografoMaestro
		} else {
			val cartografo = cartografos.getOrElseUpdate(name, new Cartografo(cartografoMaestro_posicionProcurada - offsetToMaster))
			System.out.println("time=" + time + ", name=" + name + ", pre actualizar: pos=" + cartografo.getPosicionRelativaAOrigenMundo + ", perdido=" + cartografo.perdido + ", offsetToMaster=" + offsetToMaster + ", maestro.pos=" + cartografoMaestro.getPosicionRelativaAOrigenMundo + ", maestro.proximaPosicionProcurada=" + cartografoMaestro.posicionProcurada + ", view=" + view)
			cartografo.actualizarPosicion(view, offsetToMaster)
			System.out.println("time=" + time + ", name=" + name + ", pos actualizar: pos=" + cartografo.getPosicionRelativaAOrigenMundo + ", perdido=" + cartografo.perdido)
			cartografo
		}

		// actualizar mundo 
		if (!cartografo.perdido) {
			val posicionEsquinaVista = cartografo.getPosicionRelativaAOrigenMundo - view.center
			cartecianizar(view.cells, view.size)((simbolo, x, y) ⇒ {
				import CellOccupantType._

				val pos = XY(x + posicionEsquinaVista.x, y + posicionEsquinaVista.y)
				val celdaEnPlano = celdas(pos)

				celdaEnPlano match {
					case Pared() => // dado que las celdas de tipo pared son invariantes del plano, no hace falta actualizarlas. Toda vista bien ubicada dira que las paredes siguen en el mismo lugar
					case p: Piso if p.horaUltimaActualizacion == time => // si es un piso cuyo estado ya fue actualizado, no hay que volver a actualizarlo para evitar anuncios repetidos de eventos 
					case _ => { // si es una celda de tipo desconocido o un piso cuyo estado no fue actualizado, anunciar eventos y actualizar la celda en el plano
						val celdaResultante = simbolo match {
							case Ocluded.symbol => celdaEnPlano
							case Empty.symbol => celdaEnPlano match {
								case Desconocida => Piso(Vacio)
								case Piso(opAnterior) => opAnterior match {
									case Vacio => celdaEnPlano
									case _ => anunciarDesaparicionOcupante(pos, opAnterior)
								}
								case _ => throw Inconsistencia(pos, celdaEnPlano, view, this)
							}
							case Wall.symbol => celdaEnPlano match {
								case Desconocida => Pared()
								case _ => celdaEnPlano
							}
							case GoodPlant.symbol => celdaEnPlano match {
								case Desconocida => anunciarAparicionPlanta(pos, Azucar)
								case Piso(opAnterior) => opAnterior match {
									case Azucar => celdaEnPlano
									case _ => anunciarAparicionPlanta(pos, Azucar)
								}
								case _ => throw Inconsistencia(pos, celdaEnPlano, view, this)
							}
							case BadPlant.symbol => celdaEnPlano match {
								case Desconocida => anunciarAparicionPlanta(pos, Veneno)
								case Piso(opAnterior) => opAnterior match {
									case Veneno => celdaEnPlano
									case _ => anunciarAparicionPlanta(pos, Veneno)
								}
								case _ => throw Inconsistencia(pos, celdaEnPlano, view, this)
							}
							case GoodBeast.symbol => celdaEnPlano match {
								case Desconocida => anunciarLlegadaOAparicionDeRastreable(pos, Incierto, Vaca())
								case Piso(opAnterior) => opAnterior match {
									case Vaca() => anunciarInvarianteDeRastreable(pos, celdaEnPlano)
									case _ => anunciarLlegadaOAparicionDeRastreable(pos, opAnterior, Vaca())
								}
								case _ => throw Inconsistencia(pos, celdaEnPlano, view, this)
							}
							case BadBeast.symbol => celdaEnPlano match {
								case Desconocida => anunciarLlegadaOAparicionDeRastreable(pos, Incierto, Leon())
								case Piso(opAnterior) => opAnterior match {
									case Leon() => anunciarInvarianteDeRastreable(pos, celdaEnPlano)
									case _ => anunciarLlegadaOAparicionDeRastreable(pos, opAnterior, Leon())
								}
								case _ => throw Inconsistencia(pos, celdaEnPlano, view, this)
							}
							case EnemyMaster.symbol => celdaEnPlano match {
								case Desconocida => anunciarLlegadaOAparicionDeRastreable(pos, Incierto, ReyEnemigo())
								case Piso(opAnterior) => opAnterior match {
									case ReyEnemigo() => anunciarInvarianteDeRastreable(pos, celdaEnPlano)
									case _ => anunciarLlegadaOAparicionDeRastreable(pos, opAnterior, ReyEnemigo())
								}
								case _ => throw Inconsistencia(pos, celdaEnPlano, view, this)
							}
							case EnemySlave.symbol => celdaEnPlano match {
								case Desconocida => anunciarLlegadaOAparicionDeRastreable(pos, Incierto, SiervoEnemigo())
								case Piso(opAnterior) => opAnterior match {
									case ReyEnemigo() => anunciarInvarianteDeRastreable(pos, celdaEnPlano)
									case _ => anunciarLlegadaOAparicionDeRastreable(pos, opAnterior, SiervoEnemigo())
								}
								case _ => throw Inconsistencia(pos, celdaEnPlano, view, this)
							}
							case OurMaster.symbol => celdaEnPlano match {
								case Piso(ReyAmigo(_)) => celdaEnPlano
								case Piso(opAnterior) => anunciarArrivo(pos, opAnterior, ReyAmigo(cartografoMaestro_nombre))
								case Desconocida => Piso(ReyAmigo(cartografoMaestro_nombre)) // solo ocurre cuando time == 0
								case _ => throw Inconsistencia(pos, celdaEnPlano, view, this)
							}
							case OurSlave.symbol => celdaEnPlano match {
								case Piso(SiervoAmigo(nombreOcupanteAnterior)) => {
									val nombreNuevoOcupante = averiguarCualCartografoEstaEn(pos)
									if (nombreOcupanteAnterior == nombreNuevoOcupante) celdaEnPlano
									else anunciarArrivo(pos, SiervoAmigo(nombreOcupanteAnterior), SiervoAmigo(nombreNuevoOcupante))
								}
								case Piso(opAnterior) => anunciarArrivo(pos, opAnterior, SiervoAmigo(averiguarCualCartografoEstaEn(pos)))
								case Desconocida => Piso(SiervoAmigo(averiguarCualCartografoEstaEn(pos))) // solo ocurre cuando el cartografo sufrió un wrap y estaba perdido en el ciclo anterior
								case _ => throw Inconsistencia(pos, celdaEnPlano, view, this)
							}
							case _ ⇒ throw new AssertionError
						}

						// si la celda es de tipo variante y la vista mostro el estado actual, marcarla como actualizada (para evitar volver a tratarla).
						celdaResultante match {
							case p: Piso if simbolo != Ocluded.symbol => p.horaUltimaActualizacion = time
							case _ =>
						}
						if (celdaResultante ne celdaEnPlano) {
							celdas(x + posicionEsquinaVista.x, y + posicionEsquinaVista.y) = celdaResultante
						}
					}
				}
				true // para recorrer toda la vista
			})
			System.out.println("mundo actualizado=" + this)
		}
		cartografo
	}

	case class Inconsistencia(p: XY, celda: Celda, vista: View, mundo: Mundo) extends Exception("pos=" + p + ", celda=" + celda + ", vista=" + vista + ", mundo=" + mundo)

	private def anunciarDesaparicionOcupante(p: XY, opDesaparecido: OcupantePiso): Piso = {
		// TODO
		Piso(Vacio)
	}
	private def anunciarAparicionPlanta(p: XY, planta: Planta): Piso = {
		// TODO
		Piso(planta)
	}
	private def anunciarLlegadaOAparicionDeRastreable(p: XY, opAnterior: OcupantePiso, rastreable: Rastreable): Piso = {
		// TODO
		Piso(rastreable)
	}
	private def anunciarInvarianteDeRastreable(p: XY, celda: Celda): Celda = {
		// TODO
		celda
	}
	private def anunciarArrivo(p: XY, opAnterior: OcupantePiso, ea: EnteAmigo): Piso = {
		// TODO
		Piso(ea)
	}

	private def averiguarCualCartografoEstaEn(pos: XY): String = {
		"" // TODO
	}

	//private object cartografos extends mutable.Map[String, Cartografo] with SynchronizedMap[String, Cartografo] {}
	private object cartografos { // TODO averiguar como se hace esto usando SynchronizedMap como mixin
		private[this] val cartografos: mutable.Map[String, Cartografo] = mutable.Map()
		def apply(name: String): Cartografo = this.synchronized { cartografos(name) }
		def put(name: String, cartografo: Cartografo): Option[Cartografo] = this.synchronized { cartografos.put(name, cartografo) }
		def getOrElseUpdate(name: String, cartografo: => Cartografo): Cartografo = this.synchronized { cartografos.getOrElseUpdate(name, cartografo) }
	}
	class Cartografo(pos: XY) {
		/**El origen del mundo el la posicion que tuvo el centro de la vista del cartografo maestro a la hora 0 (inicio de la ronda). */
		private var posicionRelativaAOrigenMundo: XY = pos
		def getPosicionRelativaAOrigenMundo: XY = posicionRelativaAOrigenMundo

		/** valor del argumento que fue enviado a [[ReactDlg#move(XY)}} en el ciclo inmediato anterior al actual. @see [[#setDireccionMovProcurado]]*/
		private[Mundo] var direccionMovProcurado: XY = XY.Zero
		/** debe ser llamada desde [[ReactDlg.move(XY)]] y recibir lo mismo que el recibió */
		def setDireccionMovProcurado(direccionMovProcurado: XY): Unit = {
			this.direccionMovProcurado = direccionMovProcurado
		}
		/** posicion hacia la cual se procuró mover este cartografo en el ciclo inmediato anterior al actual */
		def posicionProcurada = posicionRelativaAOrigenMundo + direccionMovProcurado

		private var offsetToMasterAnterior: XY = XY.Zero
		var perdido: Boolean = false
		var msgApartamientoInesperado: String = null

		/** Dado que existe la posibilidad de que el movimiento efectivo difiera del procurado (cuando hay un rebote), no se puede confiar en la posición procurada por el cartografo maestro (y que es usada por los otros cartografos como referencia). Para averiguar con certeza su posicion esta operacion halla la ubicación usando las paredes como referencia. */
		def actualizarPosicion(vista: View, offsetToMasterActual: XY): Unit = {
			if (this eq cartografoMaestro) {
				if (!perdido) {
					resolverPosicion(vista, posicionProcurada, Set(direccionMovProcurado), offsetToMasterActual, "caso1")
				} else {
					//TODO
				}
			} // Si el cartografo maestro no esta perdido, utilizarlo como referencia considerando que su movimiento procurado pudo no haberse efectuado
			else if (!cartografoMaestro_perdido) {
				resolverPosicion(vista, cartografoMaestro_posicionProcurada - offsetToMasterActual, Set(cartografoMaestro_direccionMovProcurado), offsetToMasterActual, "caso3")
			} // Si el cartografo maestro esta perdido y este cartografó esta ubicado
			else if (!perdido) {
				resolverPosicion(vista, posicionProcurada, Set(direccionMovProcurado), offsetToMasterActual, "caso4")
			} // si ambos estan perdidos, esperar a que el maestro se ubique.

			this.offsetToMasterAnterior = offsetToMasterActual
		}

		/**Actualiza la posicion de este cartografo a partir de la posicion esperada y los apartamientos posibles. */
		private[this] def resolverPosicion(vista: View, posicionEsperada: XY, apartamientosPosibles: Set[XY], offsetToMaster: XY, caso: String) = {
			val apartamiento = Cartografo.averiguarApartamiento(vista, posicionEsperada, apartamientosPosibles)
			if (apartamiento.isDefined) {
				val posicionCorrecta = posicionEsperada - apartamiento.get
				chequearEmpalmes(offsetToMaster, posicionCorrecta, posicionRelativaAOrigenMundo, perdido, vista)
				perdido = false
				posicionRelativaAOrigenMundo = posicionCorrecta;
			} else {
				posicionRelativaAOrigenMundo = posicionEsperada
				perdido = true
				msgApartamientoInesperado = caso
			}
		}

		/**Se fija si el offsetToMaster sufrió un wrap tanton en X como en Y. En caso afirmativo empalma el eje correspondiente. */
		private def chequearEmpalmes(offsetToMasterActual: XY, posicionActual: XY, posicionAnterior: XY, estabaPerdido: Boolean, vista: View) {
			if (!celdas.ejeXEstaEmpalmado) {
				chequearEmpalme(XY.Right, offsetToMasterActual, posicionActual, posicionAnterior, estabaPerdido, vista)
			}
			if (!celdas.ejeYEstaEmpalmado) {
				chequearEmpalme(XY.Down, offsetToMasterActual, posicionActual, posicionAnterior, estabaPerdido, vista)
			}
		}

		private def chequearEmpalme(versorEje: XY, offsetToMasterActual: XY, posicionActual: XY, posicionAnterior: XY, estabaPerdido: Boolean, vista: View) {
			def salto = versorEje * (offsetToMasterActual - this.offsetToMasterAnterior)
			if (salto >= TAMAÑO_MUNDO_MINIMO) {
				if (estabaPerdido)
					recomendarMovimientoParaPoderResolverWrap(direccionMovProcurado.negated)
				else {
					// averiguar posicionActualWrapeada usando 'averiguarApartamiento' con la vista actual, la posicion anterior, y el movimiento procurado entre el ciclo anterior y este
					val posicionActualWrapeada = {
						val apartamiento = Cartografo.averiguarApartamiento(vista, posicionAnterior, Set(this.direccionMovProcurado))
						posicionAnterior + this.direccionMovProcurado - apartamiento.get
					}
					val periodo = versorEje * (posicionActualWrapeada - posicionActual)
					celdas.empalmar(versorEje, periodo)
				}
			}
		}

	}
	private object Cartografo {
		//		/**Se asume que el apartamiento nunca es mayor a una celda de distancia  */
		//		private val apartamientosDelCursoProcuradoPosibles = Direction45.values.map((x: Direction45.Value) ⇒ XY.fromDirection45(x.id)) + XY.Zero

		def dePosRelativaAEsquinaVistaAPosRelativaAOrigenMundo(posRelativaAEsquinaVista: XY, posCentroVistaRelativaAEsquinaVista: XY, posCentroVistaRelativaAOrigenMundo: XY): XY =
			posRelativaAEsquinaVista - posCentroVistaRelativaAEsquinaVista + posCentroVistaRelativaAOrigenMundo

		/**
		 * Utilizada para averiguar la posicion del centro de la vista (respecto al origen del plano) tal que las paredes en la vista encajen con las paredes del plano.
		 * No prueba todas las posiciones posibles en el plano. Solo la posicion esperada y los apartamientos recibidos.
		 * @param vista vista del cartografo
		 * @param posicionEsperada posicion esperada del centro de la vista
		 * @param apartamientosPosibles apartamientos posibles de la posicion esperada respecto a la posicion correcta.
		 */
		def averiguarApartamiento(vista: View, posicionEsperada: XY, apartamientosPosibles: Set[XY]): Option[XY] = {
			var apartamientosDelCursoProcuradoHipoteticos = apartamientosPosibles + XY.Zero
			var cantMinimaCeldasComparadas = vista.size * 2 // para reducir este numero habría que hacer otra version de cartecianizar que recorra de forma mas dispersa (en espiral por ejemplo)
			cartecianizar(vista.cells, vista.size)((simbolo, x, y) => {
				val posCeldaIteradaRelativaAOrigenMundo = Cartografo.dePosRelativaAEsquinaVistaAPosRelativaAOrigenMundo(XY(x, y), vista.center, posicionEsperada)
				apartamientosDelCursoProcuradoHipoteticos = apartamientosDelCursoProcuradoHipoteticos.filter(dh => {
					celdas(posCeldaIteradaRelativaAOrigenMundo - dh) match {
						case Desconocida => true
						case Pared() => simbolo == CellOccupantType.Wall.symbol || simbolo == CellOccupantType.Ocluded.symbol
						case Piso(_) => simbolo != CellOccupantType.Wall.symbol
					}
				})
				cantMinimaCeldasComparadas -= 1
				apartamientosDelCursoProcuradoHipoteticos.size > 1 || cantMinimaCeldasComparadas >= 0
			})
			if (apartamientosDelCursoProcuradoHipoteticos.size == 1)
				Some(apartamientosDelCursoProcuradoHipoteticos.head)
			else
				None
		}
	}

	/** Igual a {@link Traversable.forall} excepto que p recibe, ademas del elemento iterado, las coordenadas x e y que le corresponden a tal elemento si el valor de un punto (x,y) en el plano fuera c(y*xSize+x) */
	private def cartecianizar[T](c: scala.collection.Traversable[T], xSize: Int)(p: (T, Int, Int) ⇒ Boolean): Boolean = {
		var x, y = 0
		c.forall((e: T) ⇒ {
			val continuar = p(e, x, y)
			x += 1
			if (x >= xSize) {
				x = 0
				y += 1
			}
			continuar
		})
	}

	sealed trait Celda {
		val simbolo: Char
		override def toString: String = simbolo.toString
	}
	case object Desconocida extends Celda {
		override val simbolo = '~'
	}
	case class Pared() extends Celda {
		override val simbolo = 'W'
		@volatile var tieneCaraNorte: Trivalente = Trivalente.Incierto
		@volatile var tieneCaraSur: Trivalente = Trivalente.Incierto
		@volatile var tieneCaraEste: Trivalente = Trivalente.Incierto
		@volatile var tieneCaraOeste: Trivalente = Trivalente.Incierto
	}
	case class Piso(op: OcupantePiso) extends Celda {
		def this(horaUltimaActualizacion: Int, op: OcupantePiso) {
			this(op)
			this.horaUltimaActualizacion = horaUltimaActualizacion
		}
		override val simbolo = op.simbolo
		@volatile var horaUltimaActualizacion: Int = Integer.MIN_VALUE
	}

	sealed trait OcupantePiso {
		val simbolo: Char
	}
	sealed trait Navegable extends OcupantePiso
	sealed trait Planta extends Navegable
	sealed trait Rastreable extends OcupantePiso {
		val periodoReaccion: Int
		var id: Option[String] = None
		@volatile var rastro: Seq[XY] = Seq()
	}
	sealed trait EnteAmigo extends OcupantePiso {
		val nombre: String
	}

	case object Incierto extends OcupantePiso {
		override val simbolo = '?'
	}
	case object Vacio extends Navegable {
		override val simbolo = '_'
	}
	case object Azucar extends Planta {
		override val simbolo = 'P'
	}
	case object Veneno extends Planta {
		override val simbolo = 'p'
	}
	case class Vaca() extends Rastreable with Navegable {
		override val simbolo = 'B'
		override val periodoReaccion = 4
	}
	case class Leon() extends Rastreable {
		override val simbolo = 'b'
		override val periodoReaccion = 4
	}
	case class ReyEnemigo() extends Rastreable {
		override val simbolo = 'm'
		override val periodoReaccion = 2
	}
	case class SiervoEnemigo() extends Rastreable {
		override val simbolo = 's'
		override val periodoReaccion = 1
	}
	case class ReyAmigo(nombre: String) extends EnteAmigo {
		override val simbolo = 'M'
	}
	case class SiervoAmigo(nombre: String) extends EnteAmigo {
		override val simbolo = 'S'
	}

	/////////////////////////////// Recomendaciones de Mundo para el bot //////////////////////////////

	def recomendarMovimientoParaPoderResolverWrap(direccionMov: XY) {
		//TODO
	}
}
